using System;
using System.Data;
using NHibernate;
using Castle.Core.Logging;
using Caramel;

namespace Caramel.NHibernate
{
	public class NHibernateUnitOfWork : IUnitOfWork, INHibernateUnitOfWork
	{
		//private bool _wasCommitted = false;
		//private bool _wasRolledBack = false;
		private bool _isDisposed = false;
		private ISession _session;
		private ITransaction _transaction;

		//public NHibernateUnitOfWork(ISession session, string name)
		//    : this(session, name, IsolationLevel.ReadCommitted)
		//{
		//}

		public NHibernateUnitOfWork(ISession session, string name, IsolationLevel isolationLevel)
		{
			this.Logger = NullLogger.Instance;
			this.Name = name;
			this.ID = Guid.NewGuid();
			this.Session = session;
			_transaction = this.Session.BeginTransaction(isolationLevel);
		}

		public Castle.Core.Logging.ILogger Logger { get; set; }

		#region INHibernateUnitOfWork Members

		public ISession Session
		{
			get
			{
				this.EnsureNotDisposed();

				return _session;
			}
			private set
			{
				_session = value;
			}
		}

		#endregion

		private bool IsActive { get { return !this.WasCommitted && !this.WasRolledBack; } }

		#region IUnitOfWork Members

		public string Name { get; private set; }

		public Guid ID { get; private set; }

		public bool WasCommitted { get; private set; }

		public bool WasRolledBack { get; private set; }

		public void Commit()
		{
			this.Logger.InfoFormat("Committing {0}: {1}...".FormatWith(this.GetType().Name, this.ID));
			this.EnsureIsActive();
			_session.Flush();
			_transaction.Commit();
			this.WasCommitted = true;
			this.Logger.InfoFormat("Committed {0}: {1}.".FormatWith(this.GetType().Name, this.ID));
		}

		public void Rollback()
		{
			this.Logger.InfoFormat("Rolling Back {0}: {1}...".FormatWith(this.GetType().Name, this.ID));
			this.EnsureIsActive();
			_session.Clear();
			_transaction.Rollback();
			this.WasRolledBack = true;
			this.Logger.InfoFormat("Rolled Back {0}: {1}.".FormatWith(this.GetType().Name, this.ID));
		}

		public void Evict(object entity)
		{
			this.EnsureIsActive();
			this.Session.Evict(entity);
		}

		#endregion

		#region IDisposable Members

		public void Dispose()
		{
			if (!_isDisposed)
			{
				if (_transaction.IsActive)
				{
					this.Rollback();
				}

				_transaction = null;
				this.Session.Dispose();
				this.Session = null;

				UnitOfWork.DisposeOfUnitOfWork(this);

				_isDisposed = true;
			}
		}

		#endregion

		private void EnsureIsActive()
		{
			this.EnsureWasNotCommitted();
			this.EnsureWasNotRolledBack();
			this.EnsureNotDisposed();
		}

		private void EnsureWasNotCommitted()
		{
			if (this.WasCommitted)
			{
				throw new InvalidOperationException("Work was already committed.");
			}
		}

		private void EnsureWasNotRolledBack()
		{
			if (this.WasRolledBack)
			{
				throw new InvalidOperationException("Work was already committed.");
			}
		}

		private void EnsureNotDisposed()
		{
			if (_isDisposed)
			{
				throw new ObjectDisposedException(this.GetType().Name);
			}
		}
	}
}
